<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="myCanvas" width="1080" height="630" style="border: 1px solid;"></canvas>
</body>

</html>
<script>
    const ctx = myCanvas.getContext('2d');

    let p1 = [150, 300];
    let p2 = [850, 300];
    let c1 = [400, 150];
    let c2 = [600, 400];

    /**
 * 在一段三次贝塞尔曲线上均匀取点, 不包括终点
 * @param counts 一段贝塞尔曲线中取点的数量
 */
    getPntsOf3Bezier = function (p0, p1, p2, p3, counts) {
        let per = counts && counts != 0 ? 1 / counts : 0.02;    //取点间隔
        let points = [];
        for (let t = 0; t <= 0.999999; t += per) {
            points.push(getPntIn3Bezier(p0, p1, p2, p3, t));
        }
        return points;
    };

    /**
     * 获取三次贝塞尔曲线上的一点，t∈[0,1]
     * @param t 介于0 ~ 1, 表示点在曲线中的相对位置
     */
    getPntIn3Bezier = function (p0, p1, p2, p3, t) {
        let t_ = 1 - t;
        let x = p0[0] * t_ * t_ * t_ + 3 * p1[0] * t * t_ * t_ + 3 * p2[0] * t * t * t_ + p3[0] * t * t * t,
            y = p0[1] * t_ * t_ * t_ + 3 * p1[1] * t * t_ * t_ + 3 * p2[1] * t * t * t_ + p3[1] * t * t * t;
        return [x, y];
    };


    function getVctLen([x, y]) {  // 模长
        return Math.sqrt(x * x + y * y);
    };

    /**
* 根据点在向量上的比例计算点坐标, [xO, yO]为起点，[xVct, yVct]为向量，k 为该点在向量方向上的长度
* 获取
*/
    function getPntInVct([xO, yO], [xVct, yVct], k) {
        let lenVct = getVctLen([xVct, yVct]);  // 获取向量长度
        let stdVct;
        if (lenVct === 0) {
            stdVct = [0, 0];
        } else {
            stdVct = [xVct / lenVct, yVct / lenVct];   // 单位向量
        }
        return [xO + k * stdVct[0], yO + k * stdVct[1]];
    };

    function getLenOfTwoPnts(p1, p2) {
        return Math.sqrt(Math.pow((p1[0] - p2[0]), 2) + Math.pow((p1[1] - p2[1]), 2));
    };

    vct = function (start, end) {
        let x = end[0] - start[0];
        let y = end[1] - start[1];
        return [x, y];
    };

    /**
 * 在曲线(多点折线, 按点顺序)上取长度为 len 的一点坐标, 包括点真实坐标和曲线点数组中仅次于该点的下标, 0 < k < 1;
 */
    calPntInCurveByLen = function (curvePnts, len) {
        let lenNodes = curvePnts.length;
        let P, index;
        let templen = 0, temp = 0;
        for (let j = 0; j < lenNodes - 1; j++) {
            temp = getLenOfTwoPnts(curvePnts[j], curvePnts[j + 1]);
            if (templen + temp > len) {
                P = getPntInVct(curvePnts[j], vct(curvePnts[j], curvePnts[j + 1]), len - templen);
                index = j;
                break;
            }
            templen += temp;
        }
        if (!P) {
            return null;
        }
        return [P, index];
    };


    // 获取两个向量之间的夹角
    getRotateAng = function ([x1, y1], [x2, y2]) {
        let EPSILON = 1.0e-8;
        let dist, dot, cross, degree, angle;

        dist = Math.sqrt(x1 * x1 + y1 * y1);
        x1 /= dist;
        y1 /= dist;
        dist = Math.sqrt(x2 * x2 + y2 * y2);
        x2 /= dist;
        y2 /= dist;

        dot = x1 * x2 + y1 * y2;
        if (Math.abs(dot - 1.0) <= EPSILON) {
            angle = 0;
        } else if (Math.abs(dot + 1.0) <= EPSILON) {
            angle = Math.PI;
        } else {
            angle = Math.acos(dot);
            cross = x1 * y2 - x2 * y1;
            if (cross < 0) {
                angle = 2 * Math.PI - angle;
            }
        }
        degree = angle * 180 / Math.PI;
        return degree;
    };

    /**
* 创建向量 (从起点到终点的一个向量)
*/
    function vct([x1, y1], [x2, y2]) {
        let x = x2 - x1;
        let y = y2 - y1;
        return [x, y];
    };

    var len = 0;
    ctx.textBaseline = 'middle'  // 一定要设置，不然高度平移不一致
    function draw() {
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
        let points = getPntsOf3Bezier(p1, c1, c2, p2, 100);
        points.forEach((p, i) => {
            if (i == 0) {
                ctx.moveTo(p[0], p[1]);
            } else {
                ctx.lineTo(p[0], p[1]);
            }
        })
        ctx.stroke();

        ctx.beginPath();
        ctx.fillStyle = "blue";
        ctx.arc(c1[0], c1[1], 5, 0, 2 * Math.PI);
        ctx.fill();
        ctx.fillText("控制点1", c1[0], c1[1]);

        ctx.beginPath();
        ctx.fillStyle = "blue";
        ctx.arc(c2[0], c2[1], 5, 0, 2 * Math.PI);
        ctx.fill();
        ctx.fillText("控制点2", c2[0], c2[1]);

        ctx.beginPath();
        let gpIndex = calPntInCurveByLen(points, len)[1];
        let ni = gpIndex + 1;
        let gp = calPntInCurveByLen(points, len)[0];
        let vct1 = vct(gp, points[ni]);
        let vct2 = [100, 0];
        let angle = getRotateAng(vct2, vct1);
        ctx.save();
        ctx.font = `30px Arial`
        ctx.translate(gp[0], gp[1]); //设置画布上的(0,0)位置，也就是旋转的中心点
        ctx.rotate(angle * Math.PI / 180);
        ctx.fillStyle = "#006d75";
        ctx.fillText("测", 0, -20);
        ctx.restore();
    }
    // draw()
    setInterval(draw, 0);

    window.addEventListener("keydown", (e) => {
        if (e.keyCode == 38) {
            len += 5;
        }
        if (e.keyCode == 40) {
            len -= 5;
        }
        console.log(len);
    })

</script>
<style>
</style>